// TODO : Serializable, Cloneable, Synchronization
package org.blankie.model;

import java.beans.PropertyChangeSupport;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.LinkedList;
import java.util.Collection;
import java.util.concurrent.Executor;

import org.blankie.lang.Validate;
import org.blankie.util.AsynchronousOrganizationManager;
import org.blankie.util.AsynchronousOrganizationManagerDelegate;
import org.blankie.util.Collector;
import org.blankie.util.OrganizationManager;
import org.blankie.util.ComponentLifeCycle;
import org.blankie.util.ComponentState;
import org.blankie.util.ComponentStateHolder;
import org.blankie.util.ComponentStateHolder.AbstractLifeCycleHooks;
import org.blankie.util.Receiver;
import org.blankie.model.DriverOperationListener.OperationType;
import org.blankie.util.ObjectRegistry;
import org.blankie.util.Organized;
import org.blankie.util.SynchronizedObjectRegistryWrapper;
import org.blankie.util.WeakObjectRegistry;

/**
 * Class defines a driver for <em>Blankie</em> with the following features:
 * <ol>
 * <li>It implements the interface <code>ComponentLifeCycle</code>.
 * <li>It implements both <code>Cloneable</code> and <code>Serializable</code>.
 * <li>It implements the <code>AsynchronousOrganizationManager</code> interface by delegating to the
 * sub-classes using the synchrone <code>OrganizationManager</code>.
 * </ol>
 */
public abstract class BlkAbstractDriver<P extends BlkPath, O extends BlkResource<P>, N extends BlkResource<P>, L extends BlkResource<P>>
        implements OrganizationManager<O, N, L>, AsynchronousOrganizationManager<O, N, L>,
        ComponentLifeCycle, Serializable, Cloneable {

    /**
     * This class implements the <code>ComponentLifeCycleHooks</code> by calling life cycle hooks method
     * defined in the global class.
     */
    private class DriverLifeCycleHooks extends AbstractLifeCycleHooks<BlkAbstractDriver> {

        DriverLifeCycleHooks() {
            super(BlkAbstractDriver.this);
        }

        @Override
        public void doInit() throws Exception {
            holder.doInit();
        }

        @Override
        public void doStart() throws Exception {
            holder.doStart();
        }

        @Override
        public void doStop() throws Exception {
            holder.doStop();
        }

        @Override
        public void doDestroy() throws Exception {
            holder.doDestroy();
        }
    }
    
    /**
     * The <var>executor</var> property name.
     */
    public static final String PROP_EXECUTOR = "executor";
    
    /**
     * The serialization ID
     */
    private static final long serialVersionUID = 0L;
    
    /**
     * The class responsible for implementing the component life cycle by
     * delegating to the class hooks.
     */
    private ComponentStateHolder componentStateHolder;
    
    /**
     * The class responsible for executing the asynchronous processes.
     */
    private Executor executor;
    
    /**
     * The instance is responsible for converting synchrone implementation to
     * asynchrone <abbr>API</abbr> of <code>OrganizationManager</code>.
     */
    private transient AsynchronousOrganizationManagerDelegate<O, N, L> asynchronousWrapper;
    
    /**
     * The instance that manages the class properties.
     */
    private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
    
    /**
     * The objects that wants to listen to the driver operations.
     * @todo Handle the serialization of that field like PropertyChangeSupport.
     */
    private final List<DriverOperationListener> listeners = new LinkedList<DriverOperationListener>(Arrays.asList(new ValidateListener(this)));
    
    protected final ObjectRegistry<P, O> organizationRegistry = new SynchronizedObjectRegistryWrapper(new WeakObjectRegistry<P, O>());
    
    protected final ObjectRegistry<P, N> nodeRegistry = new SynchronizedObjectRegistryWrapper(new WeakObjectRegistry<P, N>());

    protected final ObjectRegistry<P, L> leafRegistry = new SynchronizedObjectRegistryWrapper(new WeakObjectRegistry<P, L>());
    
    /**
     * The protecte no-args constructor.
     */
    protected BlkAbstractDriver() {
        this.componentStateHolder = new ComponentStateHolder(new DriverLifeCycleHooks());
    }

    // The AsynchronousOrganizationManager methods delegated to the class asynchronous wrapper.
    @Override
    public void startGettingLeaves(Organized managed, Collector<L> into) {
        getAsynchronousWrapper().startGettingLeaves(managed, into);
    }

    @Override
    public void startGettingNodes(Organized managed, Collector<N> into) {
        getAsynchronousWrapper().startGettingNodes(managed, into);
    }

    @Override
    public void startGettingOrganization(Organized managed, Receiver<O> into) {
        getAsynchronousWrapper().startGettingOrganization(managed, into);
    }

    @Override
    public void startGettingParent(Organized managed, Receiver<N> into) {
        getAsynchronousWrapper().startGettingParent(managed, into);
    }

    @Override
    public void startGettingRoot(Organized managed, Receiver<N> into) {
        getAsynchronousWrapper().startGettingRoot(managed, into);
    }

    // The OrganizationManager methods delegated to the implementation..
    @Override
    public Collection<L> getLeaves(Organized managed) {
        Validate.parameterNotNullInstanceOf("managed", managed, BlkResource.class);
        Validate.equals(isNode(managed), true);
        
        N node = (N) managed;
        OperationType type = OperationType.LOAD_LEAVES;
        Collection<L> result = new ArrayList<L>();
        Exception error = null;
        
        fireDriverOperationStarted(type, node);
        try {
            List<P> paths = new ArrayList<P>();
            
            getImplementation().loadLeaves(node, paths);
            
            for (P path : paths) {
                L resource = leafRegistry.search(path);
                
                if (resource == null) {
                    resource = newLeaf(path);
                    leafRegistry.register(resource);
                }
                
                result.add(resource);
            }
        } catch (RuntimeException e) {
            error = e;
        } finally {
            fireDriverOperationTerminated(type, node, error == null ? result : error);
        }
        
        return result;
    }

    @Override
    public void getLeaves(Organized managed, Collection<? super L> into) {
        Validate.parameterNotNullInstanceOf("managed", managed, BlkResource.class);
        Validate.equals(isNode(managed), true);
        
        N node = (N) managed;
        OperationType type = OperationType.LOAD_LEAVES;
        Exception error = null;
        
        fireDriverOperationStarted(type, node);
        try {
            List<P> paths = new ArrayList<P>();
            
            getImplementation().loadLeaves(node, paths);
            
            for (P path : paths) {
                L resource = leafRegistry.search(path);
                
                if (resource == null) {
                    resource = newLeaf(path);
                    leafRegistry.register(resource);
                }
                
                into.add(resource);
            }
        } catch (RuntimeException e) {
            error = e;
        } finally {
            fireDriverOperationTerminated(type, node, error == null ? into : error);
        }
    }

    @Override
    public Collection<N> getNodes(Organized managed) {
        Validate.parameterNotNullInstanceOf("managed", managed, BlkResource.class);
        Validate.equals(isNode(managed), true);
        
        N node = (N) managed;
        OperationType type = OperationType.LOAD_NODES;
        Collection<N> result = new ArrayList<N>();
        Exception error = null;
        
        fireDriverOperationStarted(type, node);
        try {
            List<P> paths = new ArrayList<P>();
            
            getImplementation().loadNodes(node, paths);
            
            for (P path : paths) {
                N resource = nodeRegistry.search(path);
                
                if (resource == null) {
                    resource = newNode(path);
                    nodeRegistry.register(resource);
                }
                
                result.add(resource);
            }
        } catch (RuntimeException e) {
            error = e;
        } finally {
            fireDriverOperationTerminated(type, node, error == null ? result : error);
        }
        
        return result;
    }

    @Override
    public void getNodes(Organized managed, Collection<? super N> into) {
        Validate.parameterNotNullInstanceOf("managed", managed, BlkResource.class);
        Validate.equals(isNode(managed), true);
        
        N node = (N) managed;
        OperationType type = OperationType.LOAD_NODES;
        Exception error = null;
        
        fireDriverOperationStarted(type, node);
        try {
            List<P> paths = new ArrayList<P>();
            
            getImplementation().loadNodes(node, paths);
            
            for (P path : paths) {
                N resource = nodeRegistry.search(path);
                
                if (resource == null) {
                    resource = newNode(path);
                    nodeRegistry.register(resource);
                }
                
                into.add(resource);
            }
        } catch (RuntimeException e) {
            error = e;
        } finally {
            fireDriverOperationTerminated(type, node, error == null ? into : error);
        }
    }

    @Override
    public O getOrganization(Organized managed) {
        Validate.parameterNotNullInstanceOf("managed", managed, BlkResource.class);
        
        BlkResource<P> resource = (BlkResource<P>) managed;
        OperationType type = OperationType.FIND_ORGANIZATION;
        O result = null;
        Exception error = null;
        
        fireDriverOperationStarted(type, resource);
        try {
            P path = getImplementation().findOrganization(resource.getId());
            
            result = organizationRegistry.search(path);

            if (result == null) {
                result = newOrganization(path);
                organizationRegistry.register(result);
            }
        } catch (RuntimeException e) {
            error = e;
        } finally {
            fireDriverOperationTerminated(type, resource, error == null ? result : error);
        }
        
        return result;
    }

    @Override
    public N getParent(Organized managed) {
        Validate.parameterNotNullInstanceOf("managed", managed, BlkResource.class);
        
        BlkResource<P> resource = (BlkResource<P>) managed;
        OperationType type = OperationType.FIND_PARENT;
        N result = null;
        Exception error = null;
        
        
        fireDriverOperationStarted(type, resource);
        try {
            P path = getImplementation().findParent(resource.getId());
            
            result = nodeRegistry.search(path);

            if (result == null) {
                result = newNode(path);
                nodeRegistry.register(result);
            }
        } catch (RuntimeException e) {
            error = e;
        } finally {
            fireDriverOperationTerminated(type, resource, error == null ? result : error);
        }
        
        return result;
    }

    @Override
    public N getRoot(Organized managed) {
        Validate.parameterNotNullInstanceOf("managed", managed, BlkResource.class);
        
        BlkResource<P> resource = (BlkResource<P>) managed;
        OperationType type = OperationType.FIND_ROOT;
        N result = null;
        Exception error = null;
        
        
        fireDriverOperationStarted(type, resource);
        try {
            P path = getImplementation().findRoot(resource.getId());
            
            result = nodeRegistry.search(path);

            if (result == null) {
                result = newNode(path);
                nodeRegistry.register(result);
            }
        } catch (RuntimeException e) {
            error = e;
        } finally {
            fireDriverOperationTerminated(type, resource, error == null ? result : error);
        }
        
        return result;
    }

    @Override
    public boolean hasNodes(Organized managed) {
        Validate.parameterNotNullInstanceOf("managed", managed, BlkResource.class);
        Validate.equals(isNode(managed), true);
        Exception error = null;
        
        
        N node = (N) managed;
        OperationType type = OperationType.TEST_NODES;
        boolean result = false;
        
        fireDriverOperationStarted(type, node);
        try {
            result = getImplementation().testNodes(node);
        } catch (RuntimeException e) {
            error = e;
        } finally {
            fireDriverOperationTerminated(type, node, error == null ? result : error);
        }
        
        return result;
    }

    @Override
    public boolean hasLeaves(Organized managed) {
        Validate.parameterNotNullInstanceOf("managed", managed, BlkResource.class);
        Validate.equals(isNode(managed), true);
        Exception error = null;
        
        N node = (N) managed;
        OperationType type = OperationType.TEST_LEAVES;
        boolean result = false;
        
        fireDriverOperationStarted(type, node);
        try {
            result = getImplementation().testLeaves(node);
        } catch (RuntimeException e) {
            error = e;
        } finally {
            fireDriverOperationTerminated(type, node, error == null ? result : error);
        }
        
        return result;
    }

    @Override
    public boolean isRoot(Organized managed) {
        Validate.parameterNotNullInstanceOf("managed", managed, BlkResource.class);
        Validate.equals(isNode(managed), true);
        
        N node = (N) managed;
        OperationType type = OperationType.TEST_ROOT;
        boolean result = false;
        Exception error = null;
        
        
        fireDriverOperationStarted(type, node);
        try {
            result = getImplementation().testRoot(node);
        } catch (RuntimeException e) {
            error = e;
        } finally {
            fireDriverOperationTerminated(type, node, error == null ? result : error);
        }
        
        return result;
    }

    // The LifeCycle methods delegated to the class component state holder.
    
    @Override
    public boolean init() {
        return componentStateHolder.init();
    }

    @Override
    public boolean start() {
        return componentStateHolder.start();
    }

    @Override
    public boolean stop() {
        return componentStateHolder.stop();
    }

    @Override
    public boolean restart() {
        return componentStateHolder.restart();
    }

    @Override
    public void destroy() {
        componentStateHolder.destroy();
    }

    @Override
    public ComponentState getState() {
        return componentStateHolder.getState();
    }

    @Override
    public boolean isInitialized() {
        return componentStateHolder.isInitialized();
    }

    // The listeners management methods.
    
    public void addListener(DriverOperationListener listener) {
        if (listener != null) {
            listeners.add(listener);
        }
    }

    public void removeListener(DriverOperationListener listener) {
        for (Iterator<DriverOperationListener> i = listeners.iterator(); i.hasNext();) {
            if (i.next() == listener) {
                i.remove();
            }
        }
    }

    // The internal listener management methods.
    
    protected synchronized void fireDriverOperationStarted(OperationType type, BlkResource<P> object) {
        Validate.parameterNotNull("type", type);

        for (DriverOperationListener listener : listeners) {
            listener.operationStarted(type, object);
        }
    }

    protected synchronized void fireDriverOperationTerminated(OperationType type, BlkResource<P> object, Object result) {
        Validate.parameterNotNull("type", type);

        for (DriverOperationListener listener : listeners) {
            listener.operationTerminated(type, object, result);
        }
    }
    
    // Factory methods.
    
    protected abstract L newLeaf(P path);
    
    protected abstract N newNode(P path);
    
    protected abstract O newOrganization(P path);

    // The default LifeCycleHooks method intended to be overriden by sub-classes.
    protected void doInit() throws Exception {
        return;
    }

    protected void doStart() throws Exception {
        return;
    }

    protected void doStop() throws Exception {
        return;
    }

    protected void doDestroy() throws Exception {
        return;
    }

    // The JavaBean methods.
    /**
     * Add PropertyChangeListener.
     *
     * @param listener
     */
    public void addPropertyChangeListener(java.beans.PropertyChangeListener listener) {
        propertyChangeSupport.addPropertyChangeListener(listener);
    }

    /**
     * Remove PropertyChangeListener.
     *
     * @param listener
     */
    public void removePropertyChangeListener(java.beans.PropertyChangeListener listener) {
        propertyChangeSupport.removePropertyChangeListener(listener);
    }

    // The class properties.
    /**
     * Get the value of executor
     *
     * @return the value of executor
     */
    public Executor getExecutor() {
        return executor;
    }

    /**
     * Set the value of executor
     *
     * @param executor new value of executor
     */
    public void setExecutor(Executor executor) {
        Validate.parameterNotNullInstanceOf("executor", executor, Serializable.class, Cloneable.class);
        Executor oldExecutor = this.executor;
        this.executor = executor;

        if (asynchronousWrapper != null) {
            asynchronousWrapper.setExecutor(executor);
        }

        propertyChangeSupport.firePropertyChange(PROP_EXECUTOR, oldExecutor, executor);
    }

    // The abstract methods.
    protected abstract DriverOperations<P, O, N, L> getImplementation();

    // Lazy initializers
    AsynchronousOrganizationManager getAsynchronousWrapper() {
        if (asynchronousWrapper == null) {
            asynchronousWrapper = new AsynchronousOrganizationManagerDelegate<O, N, L>(this);
            asynchronousWrapper.setExecutor(executor);
        }

        return asynchronousWrapper;
    }
}
